From a94ea9a4e338f98750550ce7c17ce0c4bff80b79 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 8 Apr 2011 16:08:28 +0200 Subject: [PATCH] css: Overhaul value parsing Value parsing only sometimes emitted errors. Sometimes it didn't emit errors but ignored the value, sometimes it took a default, sometimes it converted it to something it deemed suitable. While refactoring, I moved the whole GValue <=> char * conversion routines to a separate file, to make navigating the core css provider easier. --- gtk/Makefile.am | 2 + gtk/gtkcssprovider.c | 1599 ++++---------------------------- gtk/gtkcssstringfuncs.c | 1543 ++++++++++++++++++++++++++++++ gtk/gtkcssstringfuncsprivate.h | 43 + 4 files changed, 1791 insertions(+), 1396 deletions(-) create mode 100644 gtk/gtkcssstringfuncs.c create mode 100644 gtk/gtkcssstringfuncsprivate.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index b5587635b7..6ca52ec685 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -387,6 +387,7 @@ gtk_private_h_sources = \ gtkbuttonprivate.h \ gtkcellareaboxcontextprivate.h \ gtkcssproviderprivate.h \ + gtkcssstringfuncsprivate.h \ gtkcustompaperunixdialog.h \ gtkdndcursors.h \ gtkentryprivate.h \ @@ -512,6 +513,7 @@ gtk_base_c_sources = \ gtkcomboboxtext.c \ gtkcontainer.c \ gtkcssprovider.c \ + gtkcssstringfuncs.c \ gtkdialog.c \ gtkdrawingarea.c \ gtkeditable.c \ diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c index 7ec2d30167..c03e7493d6 100644 --- a/gtk/gtkcssprovider.c +++ b/gtk/gtkcssprovider.c @@ -27,10 +27,8 @@ #include "gtkcssproviderprivate.h" -#include "gtkanimationdescription.h" -#include "gtk9slice.h" -#include "gtkgradient.h" -#include "gtkthemingengine.h" +#include "gtkcssstringfuncsprivate.h" +#include "gtksymboliccolor.h" #include "gtkstyleprovider.h" #include "gtkstylecontextprivate.h" #include "gtkbindings.h" @@ -841,13 +839,12 @@ static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface); static void scanner_apply_scope (GScanner *scanner, ParserScope scope); static void css_provider_reset_parser (GtkCssProvider *css_provider); -static gboolean css_provider_parse_value (GtkCssProvider *css_provider, - const gchar *value_str, - GValue *value); static gboolean gtk_css_provider_load_from_path_internal (GtkCssProvider *css_provider, const gchar *path, gboolean reset, GError **error); +static void gtk_css_provider_take_error (GtkCssProvider *provider, + GError *error); GQuark gtk_css_provider_error_quark (void) @@ -1486,13 +1483,17 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider, ((info->state & state) != 0 && (info->state & ~(state)) == 0))) { - const gchar *val_str; + GError *error = NULL; - val_str = g_value_get_string (val); - found = TRUE; + found = _gtk_css_value_from_string (value, + NULL, + g_value_get_string (val), + &error); - css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value); - break; + if (found) + break; + + gtk_css_provider_take_error (GTK_CSS_PROVIDER (provider), error); } } @@ -1586,19 +1587,6 @@ gtk_css_provider_take_error (GtkCssProvider *provider, g_error_free (error); } -static void -gtk_css_provider_error_literal (GtkCssProvider *provider, - GQuark domain, - gint code, - const char *message) -{ - GError *error; - - error = g_error_new_literal (domain, code, message); - - gtk_css_provider_take_error (provider, error); -} - static void gtk_css_provider_error (GtkCssProvider *provider, GQuark domain, @@ -1944,1286 +1932,177 @@ parse_selector (GtkCssProvider *css_provider, is_class = (scanner->token == '.'); - g_scanner_get_next_token (scanner); - - if (scanner->token != G_TOKEN_IDENTIFIER) - return G_TOKEN_IDENTIFIER; - - selector_path_prepend_glob (path); - selector_path_prepend_combinator (path, COMBINATOR_CHILD); - - if (is_class) - parse_classes (path, scanner->value.v_identifier); - else - { - if ((pos = strchr (scanner->value.v_identifier, '.')) != NULL) - *pos = '\0'; - - selector_path_prepend_name (path, scanner->value.v_identifier); - - /* Parse any remaining classes */ - if (pos) - parse_classes (path, pos + 1); - } - } - else if (is_widget_class_name (scanner->value.v_identifier)) - { - gchar *pos; - - if ((pos = strchr (scanner->value.v_identifier, '#')) != NULL || - (pos = strchr (scanner->value.v_identifier, '.')) != NULL) - { - gchar *type_name, *name; - gboolean is_class; - - is_class = (*pos == '.'); - - /* Widget type and name/class put together */ - name = pos + 1; - *pos = '\0'; - type_name = scanner->value.v_identifier; - - selector_path_prepend_type (path, type_name); - - /* This is only so there is a direct relationship - * between widget type and its name. - */ - selector_path_prepend_combinator (path, COMBINATOR_CHILD); - - if (is_class) - parse_classes (path, name); - else - { - if ((pos = strchr (name, '.')) != NULL) - *pos = '\0'; - - selector_path_prepend_name (path, name); - - /* Parse any remaining classes */ - if (pos) - parse_classes (path, pos + 1); - } - } - else - selector_path_prepend_type (path, scanner->value.v_identifier); - } - else if (_gtk_style_context_check_region_name (scanner->value.v_identifier)) - { - GtkRegionFlags flags = 0; - gchar *region_name; - - region_name = g_strdup (scanner->value.v_identifier); - - if (g_scanner_peek_next_token (scanner) == ':') - { - ParserSymbol symbol; - - g_scanner_get_next_token (scanner); - css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS); - - /* Check for the next token being nth-child, parse in that - * case, and fallback into common state parsing if not. - */ - if (g_scanner_peek_next_token (scanner) != G_TOKEN_SYMBOL) - return G_TOKEN_SYMBOL; - - symbol = GPOINTER_TO_INT (scanner->next_value.v_symbol); - - if (symbol == SYMBOL_FIRST_CHILD || - symbol == SYMBOL_LAST_CHILD || - symbol == SYMBOL_NTH_CHILD || - symbol == SYMBOL_SORTED_CHILD) - { - GTokenType token; - - if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE) - return token; - - css_provider_pop_scope (css_provider); - } - else - { - css_provider_pop_scope (css_provider); - selector_path_prepend_region (path, region_name, 0); - g_free (region_name); - break; - } - } - - selector_path_prepend_region (path, region_name, flags); - g_free (region_name); - } - else if (scanner->value.v_identifier[0] == '*') - selector_path_prepend_glob (path); - else - return G_TOKEN_IDENTIFIER; - - g_scanner_get_next_token (scanner); - - if (scanner->token == '>') - { - selector_path_prepend_combinator (path, COMBINATOR_CHILD); - g_scanner_get_next_token (scanner); - } - } - - if (scanner->token == ':') - { - /* Add glob selector if path is empty */ - if (selector_path_depth (path) == 0) - selector_path_prepend_glob (path); - - css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS); - - while (scanner->token == ':') - { - GTokenType token; - - if ((token = parse_pseudo_class (css_provider, scanner, path)) != G_TOKEN_NONE) - return token; - - g_scanner_get_next_token (scanner); - } - - css_provider_pop_scope (css_provider); - } - - return G_TOKEN_NONE; -} - -#define SKIP_SPACES(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s++; -#define SKIP_SPACES_BACK(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s--; - -static GtkSymbolicColor * -symbolic_color_parse_str (const gchar *string, - gchar **end_ptr) -{ - GtkSymbolicColor *symbolic_color = NULL; - gchar *str; - - str = (gchar *) string; - *end_ptr = str; - - if (str[0] == '@') - { - const gchar *end; - gchar *name; - - str++; - end = str; - - while (*end == '-' || *end == '_' || g_ascii_isalnum (*end)) - end++; - - name = g_strndup (str, end - str); - symbolic_color = gtk_symbolic_color_new_name (name); - g_free (name); - - *end_ptr = (gchar *) end; - } - else if (g_str_has_prefix (str, "lighter") || - g_str_has_prefix (str, "darker")) - { - GtkSymbolicColor *param_color; - gboolean is_lighter = FALSE; - - is_lighter = g_str_has_prefix (str, "lighter"); - - if (is_lighter) - str += strlen ("lighter"); - else - str += strlen ("darker"); - - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - param_color = symbolic_color_parse_str (str, end_ptr); - - if (!param_color) - return NULL; - - str = *end_ptr; - SKIP_SPACES (str); - *end_ptr = (gchar *) str; - - if (*str != ')') - { - gtk_symbolic_color_unref (param_color); - return NULL; - } - - if (is_lighter) - symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3); - else - symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7); - - gtk_symbolic_color_unref (param_color); - (*end_ptr)++; - } - else if (g_str_has_prefix (str, "shade") || - g_str_has_prefix (str, "alpha")) - { - GtkSymbolicColor *param_color; - gboolean is_shade = FALSE; - gdouble factor; - - is_shade = g_str_has_prefix (str, "shade"); - - if (is_shade) - str += strlen ("shade"); - else - str += strlen ("alpha"); - - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - param_color = symbolic_color_parse_str (str, end_ptr); - - if (!param_color) - return NULL; - - str = *end_ptr; - SKIP_SPACES (str); - - if (str[0] != ',') - { - gtk_symbolic_color_unref (param_color); - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - factor = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - *end_ptr = (gchar *) str; - - if (str[0] != ')') - { - gtk_symbolic_color_unref (param_color); - return NULL; - } - - if (is_shade) - symbolic_color = gtk_symbolic_color_new_shade (param_color, factor); - else - symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor); - - gtk_symbolic_color_unref (param_color); - (*end_ptr)++; - } - else if (g_str_has_prefix (str, "mix")) - { - GtkSymbolicColor *color1, *color2; - gdouble factor; - - str += strlen ("mix"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - color1 = symbolic_color_parse_str (str, end_ptr); - - if (!color1) - return NULL; - - str = *end_ptr; - SKIP_SPACES (str); - - if (str[0] != ',') - { - gtk_symbolic_color_unref (color1); - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - color2 = symbolic_color_parse_str (str, end_ptr); - - if (!color2 || *end_ptr[0] != ',') - { - gtk_symbolic_color_unref (color1); - return NULL; - } - - str = *end_ptr; - SKIP_SPACES (str); - - if (str[0] != ',') - { - gtk_symbolic_color_unref (color1); - gtk_symbolic_color_unref (color2); - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - factor = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - *end_ptr = (gchar *) str; - - if (str[0] != ')') - { - gtk_symbolic_color_unref (color1); - gtk_symbolic_color_unref (color2); - return NULL; - } - - symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor); - gtk_symbolic_color_unref (color1); - gtk_symbolic_color_unref (color2); - (*end_ptr)++; - } - else - { - GdkRGBA color; - gchar *color_str; - const gchar *end; - - end = str + 1; - - if (str[0] == '#') - { - /* Color in hex format */ - while (g_ascii_isxdigit (*end)) - end++; - } - else if (g_str_has_prefix (str, "rgb")) - { - /* color in rgb/rgba format */ - while (*end != ')' && *end != '\0') - end++; - - if (*end == ')') - end++; - } - else - { - /* Color name */ - while (*end != '\0' && - (g_ascii_isalnum (*end) || *end == ' ')) - end++; - } - - color_str = g_strndup (str, end - str); - *end_ptr = (gchar *) end; - - if (!gdk_rgba_parse (&color, color_str)) - { - g_free (color_str); - return NULL; - } - - symbolic_color = gtk_symbolic_color_new_literal (&color); - g_free (color_str); - } - - return symbolic_color; -} - -static GtkSymbolicColor * -symbolic_color_parse (const gchar *str) -{ - GtkSymbolicColor *color; - gchar *end; - - color = symbolic_color_parse_str (str, &end); - - if (*end != '\0') - { - if (color) - { - gtk_symbolic_color_unref (color); - color = NULL; - } - } - - return color; -} - -static GtkGradient * -gradient_parse_str (const gchar *str, - gchar **end_ptr) -{ - GtkGradient *gradient = NULL; - gdouble coords[6]; - gchar *end; - guint i; - - if (g_str_has_prefix (str, "-gtk-gradient")) - { - cairo_pattern_type_t type; - - str += strlen ("-gtk-gradient"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - - /* Parse gradient type */ - if (g_str_has_prefix (str, "linear")) - { - type = CAIRO_PATTERN_TYPE_LINEAR; - str += strlen ("linear"); - } - else if (g_str_has_prefix (str, "radial")) - { - type = CAIRO_PATTERN_TYPE_RADIAL; - str += strlen ("radial"); - } - else - { - *end_ptr = (gchar *) str; - return NULL; - } - - SKIP_SPACES (str); - - /* Parse start/stop position parameters */ - for (i = 0; i < 2; i++) - { - if (*str != ',') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - - if (strncmp (str, "left", 4) == 0) - { - coords[i * 3] = 0; - str += strlen ("left"); - } - else if (strncmp (str, "right", 5) == 0) - { - coords[i * 3] = 1; - str += strlen ("right"); - } - else if (strncmp (str, "center", 6) == 0) - { - coords[i * 3] = 0.5; - str += strlen ("center"); - } - else - { - coords[i * 3] = g_ascii_strtod (str, &end); - - if (str == end) - { - *end_ptr = (gchar *) str; - return NULL; - } - - str = end; - } - - SKIP_SPACES (str); - - if (strncmp (str, "top", 3) == 0) - { - coords[(i * 3) + 1] = 0; - str += strlen ("top"); - } - else if (strncmp (str, "bottom", 6) == 0) - { - coords[(i * 3) + 1] = 1; - str += strlen ("bottom"); - } - else if (strncmp (str, "center", 6) == 0) - { - coords[(i * 3) + 1] = 0.5; - str += strlen ("center"); - } - else - { - coords[(i * 3) + 1] = g_ascii_strtod (str, &end); - - if (str == end) - { - *end_ptr = (gchar *) str; - return NULL; - } - - str = end; - } - - SKIP_SPACES (str); - - if (type == CAIRO_PATTERN_TYPE_RADIAL) - { - /* Parse radius */ - if (*str != ',') - { - *end_ptr = (gchar *) str; - return NULL; - } - - str++; - SKIP_SPACES (str); - - coords[(i * 3) + 2] = g_ascii_strtod (str, &end); - str = end; - - SKIP_SPACES (str); - } - } - - if (type == CAIRO_PATTERN_TYPE_LINEAR) - gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]); - else - gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2], - coords[3], coords[4], coords[5]); - - while (*str == ',') - { - GtkSymbolicColor *color; - gdouble position; - - if (*str != ',') - { - *end_ptr = (gchar *) str; - return gradient; - } - - str++; - SKIP_SPACES (str); - - if (g_str_has_prefix (str, "from")) - { - position = 0; - str += strlen ("from"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return gradient; - } - } - else if (g_str_has_prefix (str, "to")) - { - position = 1; - str += strlen ("to"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return gradient; - } - } - else if (g_str_has_prefix (str, "color-stop")) - { - str += strlen ("color-stop"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return gradient; - } - - str++; - SKIP_SPACES (str); - - position = g_ascii_strtod (str, &end); - - str = end; - SKIP_SPACES (str); - - if (*str != ',') - { - *end_ptr = (gchar *) str; - return gradient; - } - } - else - { - *end_ptr = (gchar *) str; - return gradient; - } - - str++; - SKIP_SPACES (str); - - color = symbolic_color_parse_str (str, &end); - - str = end; - SKIP_SPACES (str); - - if (*str != ')') - { - if (color) - gtk_symbolic_color_unref (color); - *end_ptr = (gchar *) str; - return gradient; - } - - str++; - SKIP_SPACES (str); - - if (color) - { - gtk_gradient_add_color_stop (gradient, position, color); - gtk_symbolic_color_unref (color); - } - } - - if (*str != ')') - { - *end_ptr = (gchar *) str; - return gradient; - } - - str++; - } - - *end_ptr = (gchar *) str; - - return gradient; -} - -static gchar * -path_parse_str (GtkCssProvider *css_provider, - const gchar *str, - gchar **end_ptr) -{ - gchar *path, *chr; - const gchar *start, *end; - start = str; - - if (g_str_has_prefix (str, "url")) - { - str += strlen ("url"); - SKIP_SPACES (str); - - if (*str != '(') - { - *end_ptr = (gchar *) str; - return NULL; - } - - chr = strchr (str, ')'); - if (!chr) - { - *end_ptr = (gchar *) str; - return NULL; - } - - end = chr + 1; - - str++; - SKIP_SPACES (str); - - if (*str == '"' || *str == '\'') - { - const gchar *p; - p = str; - str++; - - chr--; - SKIP_SPACES_BACK (chr); - - if (*chr != *p || chr == p) - { - *end_ptr = (gchar *)str; - return NULL; - } - } - else - { - *end_ptr = (gchar *)str; - return NULL; - } - - path = g_strndup (str, chr - str); - g_strstrip (path); - - *end_ptr = (gchar *)end; - } - else - { - path = g_strdup (str); - *end_ptr = (gchar *)str + strlen (str); - } - - /* Always return an absolute path */ - if (!g_path_is_absolute (path)) - { - GtkCssProviderPrivate *priv; - gchar *dirname, *full_path; - - priv = css_provider->priv; - - /* Use relative path to the current CSS file path, if any */ - if (priv->scanner->input_name) - dirname = g_path_get_dirname (priv->scanner->input_name); - else - dirname = g_get_current_dir (); - - full_path = g_build_filename (dirname, path, NULL); - g_free (path); - g_free (dirname); - - path = full_path; - } - - if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) - { - gtk_css_provider_error (css_provider, G_FILE_ERROR, G_FILE_ERROR_EXIST, - "File doesn't exist: %s", path); - g_free (path); - path = NULL; - *end_ptr = (gchar *)start; - } - - return path; -} - -static gchar * -path_parse (GtkCssProvider *css_provider, - const gchar *str) -{ - gchar *path; - gchar *end; - - path = path_parse_str (css_provider, str, &end); - - if (!path) - return NULL; - - if (*end != '\0') - { - gtk_css_provider_error (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Error parsing path"); - g_free (path); - path = NULL; - } - - return path; -} - -static Gtk9Slice * -slice_parse_str (GtkCssProvider *css_provider, - const gchar *str, - gchar **end_ptr) -{ - gdouble distance_top, distance_bottom; - gdouble distance_left, distance_right; - GtkSliceSideModifier mods[2]; - GdkPixbuf *pixbuf; - Gtk9Slice *slice; - gchar *path; - gint i = 0; - GError *error = NULL; - - SKIP_SPACES (str); - - /* Parse image url */ - path = path_parse_str (css_provider, str, end_ptr); - - if (!path) - return NULL; - - str = *end_ptr; - SKIP_SPACES (str); - - /* Parse top/left/bottom/right distances */ - distance_top = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - - distance_right = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - - distance_bottom = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - - distance_left = g_ascii_strtod (str, end_ptr); - - str = *end_ptr; - SKIP_SPACES (str); - - while (*str && i < 2) - { - if (g_str_has_prefix (str, "stretch")) - { - str += strlen ("stretch"); - mods[i] = GTK_SLICE_STRETCH; - } - else if (g_str_has_prefix (str, "repeat")) - { - str += strlen ("repeat"); - mods[i] = GTK_SLICE_REPEAT; - } - else - { - g_free (path); - *end_ptr = (gchar *) str; - return NULL; - } - - SKIP_SPACES (str); - i++; - } - - *end_ptr = (gchar *) str; - - if (*str != '\0') - { - g_free (path); - return NULL; - } - - if (i != 2) - { - /* Fill in second modifier, same as the first */ - mods[1] = mods[0]; - } - - pixbuf = gdk_pixbuf_new_from_file (path, &error); - g_free (path); - - if (!pixbuf) - { - gtk_css_provider_take_error (css_provider, error); - *end_ptr = (gchar *) str; - return NULL; - } - - slice = _gtk_9slice_new (pixbuf, - distance_top, distance_bottom, - distance_left, distance_right, - mods[0], mods[1]); - g_object_unref (pixbuf); - - return slice; -} - -static gdouble -unit_parse_str (const gchar *str, - gchar **end_str) -{ - gdouble unit; - - SKIP_SPACES (str); - unit = g_ascii_strtod (str, end_str); - str = *end_str; - - /* Now parse the unit type, if any. We - * don't admit spaces between these. - */ - if (*str != ' ' && *str != '\0') - { - while (**end_str != ' ' && **end_str != '\0') - (*end_str)++; - - /* Only handle pixels at the moment */ - if (strncmp (str, "px", 2) != 0) - { - gchar *type; - - type = g_strndup (str, *end_str - str); - g_warning ("Unknown unit '%s', only pixel units are " - "currently supported in CSS style", type); - g_free (type); - } - } - - return unit; -} - -static GtkBorder * -border_parse_str (const gchar *str, - gchar **end_str) -{ - gdouble first, second, third, fourth; - GtkBorder *border; - - border = gtk_border_new (); - - SKIP_SPACES (str); - if (!g_ascii_isdigit (*str) && *str != '-') - return border; - - first = unit_parse_str (str, end_str); - str = *end_str; - SKIP_SPACES (str); - - if (!g_ascii_isdigit (*str) && *str != '-') - { - border->left = border->right = border->top = border->bottom = (gint) first; - *end_str = (gchar *) str; - return border; - } - - second = unit_parse_str (str, end_str); - str = *end_str; - SKIP_SPACES (str); - - if (!g_ascii_isdigit (*str) && *str != '-') - { - border->top = border->bottom = (gint) first; - border->left = border->right = (gint) second; - *end_str = (gchar *) str; - return border; - } - - third = unit_parse_str (str, end_str); - str = *end_str; - SKIP_SPACES (str); - - if (!g_ascii_isdigit (*str) && *str != '-') - { - border->top = (gint) first; - border->left = border->right = (gint) second; - border->bottom = (gint) third; - *end_str = (gchar *) str; - return border; - } - - fourth = unit_parse_str (str, end_str); - - border->top = (gint) first; - border->right = (gint) second; - border->bottom = (gint) third; - border->left = (gint) fourth; - - return border; -} - -static void -resolve_binding_sets (const gchar *value_str, - GValue *value) -{ - GPtrArray *array; - gchar **bindings, **str; - - bindings = g_strsplit (value_str, ",", -1); - array = g_ptr_array_new (); - - for (str = bindings; *str; str++) - { - GtkBindingSet *binding_set; - - binding_set = gtk_binding_set_find (g_strstrip (*str)); - - if (!binding_set) - continue; - - g_ptr_array_add (array, binding_set); - } - - g_value_take_boxed (value, array); - g_strfreev (bindings); -} - -static gboolean -css_provider_parse_value (GtkCssProvider *css_provider, - const gchar *value_str, - GValue *value) -{ - GtkCssProviderPrivate *priv; - GType type; - gboolean parsed = TRUE; - gchar *end = NULL; - - priv = css_provider->priv; - type = G_VALUE_TYPE (value); + g_scanner_get_next_token (scanner); - if (type == GDK_TYPE_RGBA || - type == GDK_TYPE_COLOR) - { - GdkRGBA rgba; - GdkColor color; - - if (type == GDK_TYPE_RGBA && - gdk_rgba_parse (&rgba, value_str)) - g_value_set_boxed (value, &rgba); - else if (type == GDK_TYPE_COLOR && - gdk_color_parse (value_str, &color)) - g_value_set_boxed (value, &color); - else - { - GtkSymbolicColor *symbolic_color; + if (scanner->token != G_TOKEN_IDENTIFIER) + return G_TOKEN_IDENTIFIER; - symbolic_color = symbolic_color_parse_str (value_str, &end); + selector_path_prepend_glob (path); + selector_path_prepend_combinator (path, COMBINATOR_CHILD); - if (symbolic_color) + if (is_class) + parse_classes (path, scanner->value.v_identifier); + else { - g_value_unset (value); - g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR); - g_value_take_boxed (value, symbolic_color); + if ((pos = strchr (scanner->value.v_identifier, '.')) != NULL) + *pos = '\0'; + + selector_path_prepend_name (path, scanner->value.v_identifier); + + /* Parse any remaining classes */ + if (pos) + parse_classes (path, pos + 1); } - else - parsed = FALSE; } - } - else if (type == PANGO_TYPE_FONT_DESCRIPTION) - { - PangoFontDescription *font_desc; + else if (is_widget_class_name (scanner->value.v_identifier)) + { + gchar *pos; - font_desc = pango_font_description_from_string (value_str); - g_value_take_boxed (value, font_desc); - } - else if (type == G_TYPE_BOOLEAN) - { - if (value_str[0] == '1' || - g_ascii_strcasecmp (value_str, "true") == 0) - g_value_set_boolean (value, TRUE); - else - g_value_set_boolean (value, FALSE); - } - else if (type == G_TYPE_INT) - g_value_set_int (value, atoi (value_str)); - else if (type == G_TYPE_UINT) - g_value_set_uint (value, (guint) atoi (value_str)); - else if (type == G_TYPE_DOUBLE) - g_value_set_double (value, g_ascii_strtod (value_str, NULL)); - else if (type == G_TYPE_FLOAT) - g_value_set_float (value, (gfloat) g_ascii_strtod (value_str, NULL)); - else if (type == GTK_TYPE_THEMING_ENGINE) - { - GtkThemingEngine *engine; + if ((pos = strchr (scanner->value.v_identifier, '#')) != NULL || + (pos = strchr (scanner->value.v_identifier, '.')) != NULL) + { + gchar *type_name, *name; + gboolean is_class; - engine = gtk_theming_engine_load (value_str); - if (engine) - g_value_set_object (value, engine); - else - parsed = FALSE; - } - else if (type == GTK_TYPE_ANIMATION_DESCRIPTION) - { - GtkAnimationDescription *desc; + is_class = (*pos == '.'); - desc = _gtk_animation_description_from_string (value_str); + /* Widget type and name/class put together */ + name = pos + 1; + *pos = '\0'; + type_name = scanner->value.v_identifier; - if (desc) - g_value_take_boxed (value, desc); - else - parsed = FALSE; - } - else if (type == GTK_TYPE_BORDER) - { - GtkBorder *border; + selector_path_prepend_type (path, type_name); - border = border_parse_str (value_str, &end); - g_value_take_boxed (value, border); - } - else if (type == CAIRO_GOBJECT_TYPE_PATTERN) - { - GtkGradient *gradient; + /* This is only so there is a direct relationship + * between widget type and its name. + */ + selector_path_prepend_combinator (path, COMBINATOR_CHILD); + + if (is_class) + parse_classes (path, name); + else + { + if ((pos = strchr (name, '.')) != NULL) + *pos = '\0'; - gradient = gradient_parse_str (value_str, &end); + selector_path_prepend_name (path, name); - if (gradient) - { - g_value_unset (value); - g_value_init (value, GTK_TYPE_GRADIENT); - g_value_take_boxed (value, gradient); + /* Parse any remaining classes */ + if (pos) + parse_classes (path, pos + 1); + } + } + else + selector_path_prepend_type (path, scanner->value.v_identifier); } - else + else if (_gtk_style_context_check_region_name (scanner->value.v_identifier)) { - gchar *path; - GdkPixbuf *pixbuf; + GtkRegionFlags flags = 0; + gchar *region_name; - path = path_parse_str (css_provider, value_str, &end); + region_name = g_strdup (scanner->value.v_identifier); - if (path) + if (g_scanner_peek_next_token (scanner) == ':') { - pixbuf = gdk_pixbuf_new_from_file (path, NULL); - g_free (path); + ParserSymbol symbol; + + g_scanner_get_next_token (scanner); + css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS); + + /* Check for the next token being nth-child, parse in that + * case, and fallback into common state parsing if not. + */ + if (g_scanner_peek_next_token (scanner) != G_TOKEN_SYMBOL) + return G_TOKEN_SYMBOL; + + symbol = GPOINTER_TO_INT (scanner->next_value.v_symbol); - if (pixbuf) + if (symbol == SYMBOL_FIRST_CHILD || + symbol == SYMBOL_LAST_CHILD || + symbol == SYMBOL_NTH_CHILD || + symbol == SYMBOL_SORTED_CHILD) { - cairo_surface_t *surface; - cairo_pattern_t *pattern; - cairo_t *cr; - cairo_matrix_t matrix; - - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - gdk_pixbuf_get_width (pixbuf), - gdk_pixbuf_get_height (pixbuf)); - cr = cairo_create (surface); - gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); - cairo_paint (cr); - pattern = cairo_pattern_create_for_surface (surface); - - cairo_matrix_init_scale (&matrix, - gdk_pixbuf_get_width (pixbuf), - gdk_pixbuf_get_height (pixbuf)); - cairo_pattern_set_matrix (pattern, &matrix); - - cairo_surface_destroy (surface); - cairo_destroy (cr); - g_object_unref (pixbuf); - - g_value_take_boxed (value, pattern); + GTokenType token; + + if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE) + return token; + + css_provider_pop_scope (css_provider); } else - parsed = FALSE; + { + css_provider_pop_scope (css_provider); + selector_path_prepend_region (path, region_name, 0); + g_free (region_name); + break; + } } - else - parsed = FALSE; + + selector_path_prepend_region (path, region_name, flags); + g_free (region_name); } - } - else if (G_TYPE_IS_ENUM (type)) - { - GEnumClass *enum_class; - GEnumValue *enum_value; + else if (scanner->value.v_identifier[0] == '*') + selector_path_prepend_glob (path); + else + return G_TOKEN_IDENTIFIER; - enum_class = g_type_class_ref (type); - enum_value = g_enum_get_value_by_nick (enum_class, value_str); + g_scanner_get_next_token (scanner); - if (!enum_value) + if (scanner->token == '>') { - gtk_css_provider_error (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Unknown value '%s' for enum type '%s'", - value_str, g_type_name (type)); - parsed = FALSE; + selector_path_prepend_combinator (path, COMBINATOR_CHILD); + g_scanner_get_next_token (scanner); } - else - g_value_set_enum (value, enum_value->value); - - g_type_class_unref (enum_class); } - else if (G_TYPE_IS_FLAGS (type)) - { - GFlagsClass *flags_class; - GFlagsValue *flag_value; - guint flags = 0; - gchar *ptr; - flags_class = g_type_class_ref (type); + if (scanner->token == ':') + { + /* Add glob selector if path is empty */ + if (selector_path_depth (path) == 0) + selector_path_prepend_glob (path); - /* Parse comma separated values */ - ptr = strchr (value_str, ','); + css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS); - while (ptr && parsed) + while (scanner->token == ':') { - gchar *flag_str; - - *ptr = '\0'; - ptr++; - - flag_str = (gchar *) value_str; - flag_value = g_flags_get_value_by_nick (flags_class, - g_strstrip (flag_str)); + GTokenType token; - if (!flag_value) - { - gtk_css_provider_error (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Unknown flag '%s' for type '%s'", - value_str, g_type_name (type)); - parsed = FALSE; - } - else - flags |= flag_value->value; + if ((token = parse_pseudo_class (css_provider, scanner, path)) != G_TOKEN_NONE) + return token; - value_str = ptr; - ptr = strchr (value_str, ','); + g_scanner_get_next_token (scanner); } - /* Store last/only value */ - flag_value = g_flags_get_value_by_nick (flags_class, value_str); - - if (!flag_value) - { - gtk_css_provider_error (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Unknown flag '%s' for type '%s'", - value_str, g_type_name (type)); - parsed = FALSE; - } - else - flags |= flag_value->value; + css_provider_pop_scope (css_provider); + } - if (parsed) - g_value_set_enum (value, flags); + return G_TOKEN_NONE; +} - g_type_class_unref (flags_class); - } - else if (type == GTK_TYPE_9SLICE) - { - Gtk9Slice *slice; +static void +resolve_binding_sets (const gchar *value_str, + GValue *value) +{ + GPtrArray *array; + gchar **bindings, **str; - slice = slice_parse_str (css_provider, value_str, &end); + bindings = g_strsplit (value_str, ",", -1); + array = g_ptr_array_new (); - if (slice) - g_value_take_boxed (value, slice); - else - parsed = FALSE; - } - else + for (str = bindings; *str; str++) { - gtk_css_provider_error (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Cannot parse string '%s' for type %s", - value_str, g_type_name (type)); - parsed = FALSE; - } + GtkBindingSet *binding_set; - if (end) - SKIP_SPACES (end); + binding_set = gtk_binding_set_find (g_strstrip (*str)); - if (end && *end) - { - parsed = FALSE; + if (!binding_set) + continue; - gtk_css_provider_error_literal (css_provider, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_FAILED, - "Failed to parse value"); + g_ptr_array_add (array, binding_set); } - return parsed; + g_value_take_boxed (value, array); + g_strfreev (bindings); } static GTokenType @@ -3250,6 +2129,7 @@ parse_rule (GtkCssProvider *css_provider, { GtkSymbolicColor *color; gchar *color_name, *color_str; + GError *error = NULL; /* Directive is a color mapping */ g_scanner_get_next_token (scanner); @@ -3271,11 +2151,10 @@ parse_rule (GtkCssProvider *css_provider, } color_str = g_strstrip (scanner->value.v_identifier); - color = symbolic_color_parse (color_str); - + color = _gtk_css_parse_symbolic_color (color_str, &error); if (!color) { - gtk_css_provider_invalid_token (css_provider, "Color definition"); + gtk_css_provider_take_error (css_provider, error); return G_TOKEN_IDENTIFIER; } @@ -3295,33 +2174,53 @@ parse_rule (GtkCssProvider *css_provider, GSList *state_backup; gboolean loaded; gchar *path = NULL; + GFile *base, *actual; + char *dirname; + GError *error = NULL; css_provider_push_scope (css_provider, SCOPE_VALUE); g_scanner_get_next_token (scanner); if (scanner->token == G_TOKEN_IDENTIFIER && g_str_has_prefix (scanner->value.v_identifier, "url")) - path = path_parse (css_provider, - g_strstrip (scanner->value.v_identifier)); + path = g_strstrip (scanner->value.v_identifier); else if (scanner->token == G_TOKEN_STRING) - path = path_parse (css_provider, - g_strstrip (scanner->value.v_string)); - - if (path == NULL) + path = g_strstrip (scanner->value.v_string); + else { gtk_css_provider_invalid_token (css_provider, "File URL"); return G_TOKEN_IDENTIFIER; } + if (priv->scanner->input_name) + dirname = g_path_get_dirname (priv->scanner->input_name); + else + dirname = g_get_current_dir (); + + base = g_file_new_for_path (dirname); + g_free (dirname); + + actual = _gtk_css_parse_url (base, path, NULL, &error); + g_object_unref (base); + + if (actual == NULL) + { + gtk_css_provider_take_error (css_provider, error); + return G_TOKEN_IDENTIFIER; + } + css_provider_pop_scope (css_provider); g_scanner_get_next_token (scanner); if (scanner->token != ';') { - g_free (path); + g_object_unref (actual); return ';'; } + path = g_file_get_path (actual); + g_object_unref (actual); + /* Snapshot current parser state and scanner in order to restore after importing */ state_backup = priv->state; scanner_backup = priv->scanner; @@ -3342,10 +2241,7 @@ parse_rule (GtkCssProvider *css_provider, g_free (path); if (!loaded) - { - gtk_css_provider_invalid_token (css_provider, "File URL"); - return G_TOKEN_IDENTIFIER; - } + return G_TOKEN_IDENTIFIER; else return G_TOKEN_NONE; } @@ -3495,7 +2391,6 @@ parse_rule (GtkCssProvider *css_provider, } value_str = scanner->value.v_identifier; - SKIP_SPACES (value_str); g_strchomp (value_str); if (pspec) @@ -3524,10 +2419,6 @@ parse_rule (GtkCssProvider *css_provider, g_value_set_string (val, value_str); g_hash_table_insert (priv->cur_properties, prop, val); } - else if (!parse_func && css_provider_parse_value (css_provider, value_str, val)) - { - g_hash_table_insert (priv->cur_properties, prop, val); - } else if (parse_func) { GError *error = NULL; @@ -3539,12 +2430,38 @@ parse_rule (GtkCssProvider *css_provider, } else { - g_value_unset (val); - g_slice_free (GValue, val); - g_free (prop); + GError *error = NULL; + GFile *base; + char *dirname; + gboolean success; + + if (priv->scanner->input_name) + dirname = g_path_get_dirname (priv->scanner->input_name); + else + dirname = g_get_current_dir (); + + base = g_file_new_for_path (dirname); + g_free (dirname); + + success = _gtk_css_value_from_string (val, + base, + value_str, + &error); - gtk_css_provider_invalid_token (css_provider, "Property value"); - goto find_end_of_declaration; + g_object_unref (base); + + if (success) + { + g_hash_table_insert (priv->cur_properties, prop, val); + } + else + { + g_value_unset (val); + g_slice_free (GValue, val); + g_free (prop); + + gtk_css_provider_take_error (css_provider, error); + } } } else if (prop[0] == '-') @@ -4317,119 +3234,6 @@ gtk_css_provider_get_named (const gchar *name, return provider; } -static void -gtk_css_provider_print_value (const GValue *value, - GString * str) -{ - char *s; - - if (G_VALUE_TYPE (value) == GTK_TYPE_BORDER) - { - const GtkBorder *border = g_value_get_boxed (value); - - if (border == NULL) - g_string_append (str, "none"); - else if (border->left != border->right) - g_string_append_printf (str, "%d %d %d %d", border->top, border->right, border->bottom, border->left); - else if (border->top != border->bottom) - g_string_append_printf (str, "%d %d %d", border->top, border->right, border->bottom); - else if (border->top != border->left) - g_string_append_printf (str, "%d %d", border->top, border->right); - else - g_string_append_printf (str, "%d", border->top); - - return; - } - else if (G_VALUE_TYPE (value) == GDK_TYPE_RGBA) - { - const GdkRGBA *rgba = g_value_get_boxed (value); - if (rgba == NULL) - g_string_append (str, "none"); - else - { - s = gdk_rgba_to_string (g_value_get_boxed (value)); - g_string_append (str, s); - g_free (s); - } - return; - } - else if (G_VALUE_TYPE (value) == GTK_TYPE_SYMBOLIC_COLOR) - { - GtkSymbolicColor *color = g_value_get_boxed (value); - - if (color == NULL) - g_string_append (str, "none"); - else - { - s = gtk_symbolic_color_to_string (color); - g_string_append (str, s); - g_free (s); - } - return; - } - else if (G_VALUE_TYPE (value) == GTK_TYPE_ANIMATION_DESCRIPTION) - { - GtkAnimationDescription *desc = g_value_get_boxed (value); - - if (desc == NULL) - g_string_append (str, "none"); - else - { - s = _gtk_animation_description_to_string (desc); - g_string_append (str, s); - g_free (s); - } - return; - } - else if (G_VALUE_TYPE (value) == GTK_TYPE_GRADIENT) - { - GtkGradient *gradient = g_value_get_boxed (value); - - if (gradient == NULL) - g_string_append (str, "none"); - else - { - s = gtk_gradient_to_string (gradient); - g_string_append (str, s); - g_free (s); - } - return; - } - else if (G_VALUE_TYPE (value) == PANGO_TYPE_FONT_DESCRIPTION) - { - PangoFontDescription *desc = g_value_get_boxed (value); - - if (desc == NULL) - g_string_append (str, "none"); - else - { - s = pango_font_description_to_string (desc); - g_string_append (str, s); - g_free (s); - } - return; - } - - if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM)) - { - GEnumClass *enum_class; - GEnumValue *enum_value; - - enum_class = g_type_class_ref (G_VALUE_TYPE (value)); - enum_value = g_enum_get_value (enum_class, g_value_get_enum (value)); - - g_string_append (str, enum_value->value_nick); - - g_type_class_unref (enum_class); - return; - } - - /* fall back to boring strdup in the worst case */ - s = g_strdup_value_contents (value); - g_string_append (str, s); - g_free (s); -} - static void selector_path_print (const SelectorPath *path, GString * str) @@ -4539,6 +3343,7 @@ selector_style_info_print (const SelectorStyleInfo *info, GString *str) { GList *keys, *walk; + char *s; selector_path_print (info->path, str); @@ -4556,7 +3361,9 @@ selector_style_info_print (const SelectorStyleInfo *info, g_string_append (str, " "); g_string_append (str, name); g_string_append (str, ": "); - gtk_css_provider_print_value (value, str); + s = _gtk_css_value_to_string (value); + g_string_append (str, s); + g_free (s); g_string_append (str, ";\n"); } diff --git a/gtk/gtkcssstringfuncs.c b/gtk/gtkcssstringfuncs.c new file mode 100644 index 0000000000..f995fafe03 --- /dev/null +++ b/gtk/gtkcssstringfuncs.c @@ -0,0 +1,1543 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2010 Carlos Garnacho + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "gtkcssstringfuncsprivate.h" + +#include +#include +#include + +#include +#include + +#include "gtkcssprovider.h" + +/* the actual parsers we have */ +#include "gtkanimationdescription.h" +#include "gtk9slice.h" +#include "gtkgradient.h" +#include "gtkthemingengine.h" + +typedef gboolean (* FromStringFunc) (const char *str, + GFile *base, + GValue *value, + GError **error); +typedef char * (* ToStringFunc) (const GValue *value); + +static GHashTable *from_string_funcs = NULL; +static GHashTable *to_string_funcs = NULL; + +static void +register_conversion_function (GType type, + FromStringFunc from_string, + ToStringFunc to_string) +{ + if (from_string) + g_hash_table_insert (from_string_funcs, GSIZE_TO_POINTER (type), from_string); + if (to_string) + g_hash_table_insert (to_string_funcs, GSIZE_TO_POINTER (type), to_string); +} + +static gboolean +set_default_error (GError **error, + GType type) +{ + g_set_error (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Could not convert property value to type '%s'", + g_type_name (type)); + return FALSE; +} + +/*** IMPLEMENTATIONS ***/ + +#define SKIP_SPACES(s) while (g_ascii_isspace (*(s))) (s)++ +#define SKIP_SPACES_BACK(s) while (g_ascii_isspace (*(s))) (s)-- + +static GtkSymbolicColor * +symbolic_color_parse_str (const gchar *string, + gchar **end_ptr) +{ + GtkSymbolicColor *symbolic_color = NULL; + gchar *str; + + str = (gchar *) string; + *end_ptr = str; + + if (str[0] == '@') + { + const gchar *end; + gchar *name; + + str++; + end = str; + + while (*end == '-' || *end == '_' || g_ascii_isalnum (*end)) + end++; + + name = g_strndup (str, end - str); + symbolic_color = gtk_symbolic_color_new_name (name); + g_free (name); + + *end_ptr = (gchar *) end; + } + else if (g_str_has_prefix (str, "lighter") || + g_str_has_prefix (str, "darker")) + { + GtkSymbolicColor *param_color; + gboolean is_lighter = FALSE; + + is_lighter = g_str_has_prefix (str, "lighter"); + + if (is_lighter) + str += strlen ("lighter"); + else + str += strlen ("darker"); + + SKIP_SPACES (str); + + if (*str != '(') + { + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + param_color = symbolic_color_parse_str (str, end_ptr); + + if (!param_color) + return NULL; + + str = *end_ptr; + SKIP_SPACES (str); + *end_ptr = (gchar *) str; + + if (*str != ')') + { + gtk_symbolic_color_unref (param_color); + return NULL; + } + + if (is_lighter) + symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3); + else + symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7); + + gtk_symbolic_color_unref (param_color); + (*end_ptr)++; + } + else if (g_str_has_prefix (str, "shade") || + g_str_has_prefix (str, "alpha")) + { + GtkSymbolicColor *param_color; + gboolean is_shade = FALSE; + gdouble factor; + + is_shade = g_str_has_prefix (str, "shade"); + + if (is_shade) + str += strlen ("shade"); + else + str += strlen ("alpha"); + + SKIP_SPACES (str); + + if (*str != '(') + { + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + param_color = symbolic_color_parse_str (str, end_ptr); + + if (!param_color) + return NULL; + + str = *end_ptr; + SKIP_SPACES (str); + + if (str[0] != ',') + { + gtk_symbolic_color_unref (param_color); + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + factor = g_ascii_strtod (str, end_ptr); + + str = *end_ptr; + SKIP_SPACES (str); + *end_ptr = (gchar *) str; + + if (str[0] != ')') + { + gtk_symbolic_color_unref (param_color); + return NULL; + } + + if (is_shade) + symbolic_color = gtk_symbolic_color_new_shade (param_color, factor); + else + symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor); + + gtk_symbolic_color_unref (param_color); + (*end_ptr)++; + } + else if (g_str_has_prefix (str, "mix")) + { + GtkSymbolicColor *color1, *color2; + gdouble factor; + + str += strlen ("mix"); + SKIP_SPACES (str); + + if (*str != '(') + { + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + color1 = symbolic_color_parse_str (str, end_ptr); + + if (!color1) + return NULL; + + str = *end_ptr; + SKIP_SPACES (str); + + if (str[0] != ',') + { + gtk_symbolic_color_unref (color1); + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + color2 = symbolic_color_parse_str (str, end_ptr); + + if (!color2 || *end_ptr[0] != ',') + { + gtk_symbolic_color_unref (color1); + return NULL; + } + + str = *end_ptr; + SKIP_SPACES (str); + + if (str[0] != ',') + { + gtk_symbolic_color_unref (color1); + gtk_symbolic_color_unref (color2); + *end_ptr = (gchar *) str; + return NULL; + } + + str++; + SKIP_SPACES (str); + factor = g_ascii_strtod (str, end_ptr); + + str = *end_ptr; + SKIP_SPACES (str); + *end_ptr = (gchar *) str; + + if (str[0] != ')') + { + gtk_symbolic_color_unref (color1); + gtk_symbolic_color_unref (color2); + return NULL; + } + + symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor); + gtk_symbolic_color_unref (color1); + gtk_symbolic_color_unref (color2); + (*end_ptr)++; + } + else + { + GdkRGBA color; + gchar *color_str; + const gchar *end; + + end = str + 1; + + if (str[0] == '#') + { + /* Color in hex format */ + while (g_ascii_isxdigit (*end)) + end++; + } + else if (g_str_has_prefix (str, "rgb")) + { + /* color in rgb/rgba format */ + while (*end != ')' && *end != '\0') + end++; + + if (*end == ')') + end++; + } + else + { + /* Color name */ + while (*end != '\0' && + (g_ascii_isalnum (*end) || *end == ' ')) + end++; + } + + color_str = g_strndup (str, end - str); + *end_ptr = (gchar *) end; + + if (!gdk_rgba_parse (&color, color_str)) + { + g_free (color_str); + return NULL; + } + + symbolic_color = gtk_symbolic_color_new_literal (&color); + g_free (color_str); + } + + return symbolic_color; +} + +static gboolean +rgba_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkSymbolicColor *symbolic; + GdkRGBA rgba; + + if (gdk_rgba_parse (&rgba, str)) + { + g_value_set_boxed (value, &rgba); + return TRUE; + } + + symbolic = _gtk_css_parse_symbolic_color (str, error); + if (symbolic == NULL) + return FALSE; + + g_value_unset (value); + g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR); + g_value_take_boxed (value, symbolic); + return TRUE; +} + +static char * +rgba_value_to_string (const GValue *value) +{ + const GdkRGBA *rgba = g_value_get_boxed (value); + + if (rgba == NULL) + return g_strdup ("none"); + + return gdk_rgba_to_string (rgba); +} + +static gboolean +color_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkSymbolicColor *symbolic; + GdkColor color; + + if (gdk_color_parse (str, &color)) + { + g_value_set_boxed (value, &color); + return TRUE; + } + + symbolic = _gtk_css_parse_symbolic_color (str, error); + if (symbolic == NULL) + return FALSE; + + g_value_unset (value); + g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR); + g_value_take_boxed (value, symbolic); + return TRUE; +} + +static char * +color_value_to_string (const GValue *value) +{ + const GdkColor *color = g_value_get_boxed (value); + + if (color == NULL) + return g_strdup ("none"); + + return gdk_color_to_string (color); +} + +static gboolean +symbolic_color_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkSymbolicColor *symbolic; + + symbolic = _gtk_css_parse_symbolic_color (str, error); + if (symbolic == NULL) + return FALSE; + + g_value_take_boxed (value, symbolic); + return TRUE; +} + +static char * +symbolic_color_value_to_string (const GValue *value) +{ + GtkSymbolicColor *symbolic = g_value_get_boxed (value); + + if (symbolic == NULL) + return g_strdup ("none"); + + return gtk_symbolic_color_to_string (symbolic); +} + +static gboolean +font_description_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + PangoFontDescription *font_desc; + + font_desc = pango_font_description_from_string (str); + g_value_take_boxed (value, font_desc); + return TRUE; +} + +static char * +font_description_value_to_string (const GValue *value) +{ + const PangoFontDescription *desc = g_value_get_boxed (value); + + if (desc == NULL) + return g_strdup ("none"); + + return pango_font_description_to_string (desc); +} + +static gboolean +boolean_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + if (g_ascii_strcasecmp (str, "true") == 0 || + g_ascii_strcasecmp (str, "1") == 0) + { + g_value_set_boolean (value, TRUE); + return TRUE; + } + else if (g_ascii_strcasecmp (str, "false") == 0 || + g_ascii_strcasecmp (str, "0") == 0) + { + g_value_set_boolean (value, FALSE); + return TRUE; + } + + return set_default_error (error, G_VALUE_TYPE (value)); +} + +static char * +boolean_value_to_string (const GValue *value) +{ + if (g_value_get_boolean (value)) + return g_strdup ("true"); + else + return g_strdup ("false"); +} + +static gboolean +int_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + gint64 i; + char *end; + + i = g_ascii_strtoll (str, &end, 10); + + if (*end != '\0') + return set_default_error (error, G_VALUE_TYPE (value)); + + if (i > G_MAXINT || i < G_MININT) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Number too big"); + return FALSE; + } + + g_value_set_int (value, i); + return TRUE; +} + +static char * +int_value_to_string (const GValue *value) +{ + return g_strdup_printf ("%d", g_value_get_int (value)); +} + +static gboolean +uint_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + guint64 u; + char *end; + + u = g_ascii_strtoull (str, &end, 10); + + if (*end != '\0') + return set_default_error (error, G_VALUE_TYPE (value)); + + if (u > G_MAXUINT) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Number too big"); + return FALSE; + } + + g_value_set_uint (value, u); + return TRUE; +} + +static char * +uint_value_to_string (const GValue *value) +{ + return g_strdup_printf ("%u", g_value_get_uint (value)); +} + +static gboolean +double_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + double d; + char *end; + + d = g_ascii_strtod (str, &end); + + if (*end != '\0') + return set_default_error (error, G_VALUE_TYPE (value)); + + if (errno == ERANGE) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Number not representable"); + return FALSE; + } + + g_value_set_double (value, d); + return TRUE; +} + +static char * +double_value_to_string (const GValue *value) +{ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value)); + + return g_strdup (buf); +} + +static gboolean +float_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + double d; + char *end; + + d = g_ascii_strtod (str, &end); + + if (*end != '\0') + return set_default_error (error, G_VALUE_TYPE (value)); + + if (errno == ERANGE) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Number not representable"); + return FALSE; + } + + g_value_set_float (value, d); + return TRUE; +} + +static char * +float_value_to_string (const GValue *value) +{ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (buf, sizeof (buf), g_value_get_float (value)); + + return g_strdup (buf); +} + +static gboolean +theming_engine_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkThemingEngine *engine; + + engine = gtk_theming_engine_load (str); + if (engine == NULL) + { + g_set_error (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Themeing engine '%s' not found", str); + return FALSE; + } + + g_value_set_object (value, engine); + return TRUE; +} + +static char * +theming_engine_value_to_string (const GValue *value) +{ + GtkThemingEngine *engine; + char *name; + + engine = g_value_get_object (value); + if (engine == NULL) + return g_strdup ("none"); + + /* XXX: gtk_theming_engine_get_name()? */ + g_object_get (engine, "name", &name, NULL); + + return name; +} + +static gboolean +animation_description_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkAnimationDescription *desc; + + desc = _gtk_animation_description_from_string (str); + + if (desc == NULL) + return set_default_error (error, G_VALUE_TYPE (value)); + + g_value_take_boxed (value, desc); + return TRUE; +} + +static char * +animation_description_value_to_string (const GValue *value) +{ + GtkAnimationDescription *desc = g_value_get_boxed (value); + + if (desc == NULL) + return g_strdup ("none"); + + return _gtk_animation_description_to_string (desc); +} + +static gboolean +parse_border_value (const char *str, + gint16 *value, + const char **end, + GError **error) +{ + gint64 d; + + d = g_ascii_strtoll (str, (char **) end, 10); + + if (d > G_MAXINT16 || d < 0) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Number out of range for border"); + return FALSE; + } + + if (str == *end) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "No number given for border value"); + return FALSE; + } + + /* Skip optional unit type. + * We only handle pixels at the moment. + */ + if (strncmp (*end, "px", 2) == 0) + *end += 2; + + if (**end != '\0' && + !g_ascii_isspace (**end)) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Junk at end of border value"); + return FALSE; + } + + SKIP_SPACES (*end); + + *value = d; + return TRUE; +} + +static gboolean +border_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkBorder *border; + + border = gtk_border_new (); + + if (!parse_border_value (str, &border->top, &str, error)) + return FALSE; + + if (*str == '\0') + border->right = border->top; + else + if (!parse_border_value (str, &border->right, &str, error)) + return FALSE; + + if (*str == '\0') + border->bottom = border->top; + else + if (!parse_border_value (str, &border->bottom, &str, error)) + return FALSE; + + if (*str == '\0') + border->left = border->right; + else + if (!parse_border_value (str, &border->left, &str, error)) + return FALSE; + + if (*str != '\0') + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Junk at end of border value"); + return FALSE; + } + + g_value_take_boxed (value, border); + return TRUE; +} + +static char * +border_value_to_string (const GValue *value) +{ + const GtkBorder *border = g_value_get_boxed (value); + + if (border == NULL) + return g_strdup ("none"); + else if (border->left != border->right) + return g_strdup_printf ("%d %d %d %d", border->top, border->right, border->bottom, border->left); + else if (border->top != border->bottom) + return g_strdup_printf ("%d %d %d", border->top, border->right, border->bottom); + else if (border->top != border->left) + return g_strdup_printf ("%d %d", border->top, border->right); + else + return g_strdup_printf ("%d", border->top); +} + +static gboolean +gradient_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GtkGradient *gradient; + cairo_pattern_type_t type; + gdouble coords[6]; + gchar *end; + guint i; + + str += strlen ("-gtk-gradient"); + SKIP_SPACES (str); + + if (*str != '(') + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Expected '(' after '-gtk-gradient'"); + return FALSE; + } + + str++; + SKIP_SPACES (str); + + /* Parse gradient type */ + if (g_str_has_prefix (str, "linear")) + { + type = CAIRO_PATTERN_TYPE_LINEAR; + str += strlen ("linear"); + } + else if (g_str_has_prefix (str, "radial")) + { + type = CAIRO_PATTERN_TYPE_RADIAL; + str += strlen ("radial"); + } + else + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Gradient type must be 'radial' or 'linear'"); + return FALSE; + } + + SKIP_SPACES (str); + + /* Parse start/stop position parameters */ + for (i = 0; i < 2; i++) + { + if (*str != ',') + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Expected ','"); + return FALSE; + } + + str++; + SKIP_SPACES (str); + + if (strncmp (str, "left", 4) == 0) + { + coords[i * 3] = 0; + str += strlen ("left"); + } + else if (strncmp (str, "right", 5) == 0) + { + coords[i * 3] = 1; + str += strlen ("right"); + } + else if (strncmp (str, "center", 6) == 0) + { + coords[i * 3] = 0.5; + str += strlen ("center"); + } + else + { + coords[i * 3] = g_ascii_strtod (str, &end); + + if (str == end) + return set_default_error (error, G_VALUE_TYPE (value)); + + str = end; + } + + SKIP_SPACES (str); + + if (strncmp (str, "top", 3) == 0) + { + coords[(i * 3) + 1] = 0; + str += strlen ("top"); + } + else if (strncmp (str, "bottom", 6) == 0) + { + coords[(i * 3) + 1] = 1; + str += strlen ("bottom"); + } + else if (strncmp (str, "center", 6) == 0) + { + coords[(i * 3) + 1] = 0.5; + str += strlen ("center"); + } + else + { + coords[(i * 3) + 1] = g_ascii_strtod (str, &end); + + if (str == end) + return set_default_error (error, G_VALUE_TYPE (value)); + + str = end; + } + + SKIP_SPACES (str); + + if (type == CAIRO_PATTERN_TYPE_RADIAL) + { + /* Parse radius */ + if (*str != ',') + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Expected ','"); + return FALSE; + } + + str++; + SKIP_SPACES (str); + + coords[(i * 3) + 2] = g_ascii_strtod (str, &end); + str = end; + + SKIP_SPACES (str); + } + } + + if (type == CAIRO_PATTERN_TYPE_LINEAR) + gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]); + else + gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2], + coords[3], coords[4], coords[5]); + + while (*str == ',') + { + GtkSymbolicColor *color; + gdouble position; + + str++; + SKIP_SPACES (str); + + if (g_str_has_prefix (str, "from")) + { + position = 0; + str += strlen ("from"); + SKIP_SPACES (str); + + if (*str != '(') + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + } + else if (g_str_has_prefix (str, "to")) + { + position = 1; + str += strlen ("to"); + SKIP_SPACES (str); + + if (*str != '(') + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + } + else if (g_str_has_prefix (str, "color-stop")) + { + str += strlen ("color-stop"); + SKIP_SPACES (str); + + if (*str != '(') + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + str++; + SKIP_SPACES (str); + + position = g_ascii_strtod (str, &end); + + str = end; + SKIP_SPACES (str); + + if (*str != ',') + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + } + else + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + str++; + SKIP_SPACES (str); + + color = symbolic_color_parse_str (str, &end); + + str = end; + SKIP_SPACES (str); + + if (*str != ')') + { + if (color) + gtk_symbolic_color_unref (color); + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + str++; + SKIP_SPACES (str); + + if (color) + { + gtk_gradient_add_color_stop (gradient, position, color); + gtk_symbolic_color_unref (color); + } + } + + if (*str != ')') + { + g_object_unref (gradient); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + g_value_take_boxed (value, gradient); + return TRUE; +} + +static char * +gradient_value_to_string (const GValue *value) +{ + GtkGradient *gradient = g_value_get_boxed (value); + + if (gradient == NULL) + return g_strdup ("none"); + + return gtk_gradient_to_string (gradient); +} + +static gboolean +pattern_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + if (g_str_has_prefix (str, "-gtk-gradient")) + { + g_value_unset (value); + g_value_init (value, GTK_TYPE_GRADIENT); + return gradient_value_from_string (str, base, value, error); + } + else + { + gchar *path; + GdkPixbuf *pixbuf; + GFile *file; + + file = _gtk_css_parse_url (base, str, NULL, error); + if (file == NULL) + return FALSE; + + path = g_file_get_path (file); + g_object_unref (file); + + pixbuf = gdk_pixbuf_new_from_file (path, error); + g_free (path); + if (pixbuf == NULL) + return FALSE; + + cairo_surface_t *surface; + cairo_pattern_t *pattern; + cairo_t *cr; + cairo_matrix_t matrix; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + cr = cairo_create (surface); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + pattern = cairo_pattern_create_for_surface (surface); + + cairo_matrix_init_scale (&matrix, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + cairo_pattern_set_matrix (pattern, &matrix); + + cairo_surface_destroy (surface); + cairo_destroy (cr); + g_object_unref (pixbuf); + + g_value_take_boxed (value, pattern); + } + + return TRUE; +} + +static gboolean +slice_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + gdouble distance_top, distance_bottom; + gdouble distance_left, distance_right; + GtkSliceSideModifier mods[2]; + GdkPixbuf *pixbuf; + Gtk9Slice *slice; + GFile *file; + gint i = 0; + char *path; + + SKIP_SPACES (str); + + /* Parse image url */ + file = _gtk_css_parse_url (base, str, (char **) &str, error); + if (!file) + return FALSE; + + SKIP_SPACES (str); + + /* Parse top/left/bottom/right distances */ + distance_top = g_ascii_strtod (str, (char **) &str); + + SKIP_SPACES (str); + + distance_right = g_ascii_strtod (str, (char **) &str); + + SKIP_SPACES (str); + + distance_bottom = g_ascii_strtod (str, (char **) &str); + + SKIP_SPACES (str); + + distance_left = g_ascii_strtod (str, (char **) &str); + + SKIP_SPACES (str); + + while (*str && i < 2) + { + if (g_str_has_prefix (str, "stretch")) + { + str += strlen ("stretch"); + mods[i] = GTK_SLICE_STRETCH; + } + else if (g_str_has_prefix (str, "repeat")) + { + str += strlen ("repeat"); + mods[i] = GTK_SLICE_REPEAT; + } + else + { + g_object_unref (file); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + SKIP_SPACES (str); + i++; + } + + if (*str != '\0') + { + g_object_unref (file); + return set_default_error (error, G_VALUE_TYPE (value)); + } + + if (i != 2) + { + /* Fill in second modifier, same as the first */ + mods[1] = mods[0]; + } + + path = g_file_get_path (file); + pixbuf = gdk_pixbuf_new_from_file (path, error); + g_free (path); + if (!pixbuf) + return FALSE; + + slice = _gtk_9slice_new (pixbuf, + distance_top, distance_bottom, + distance_left, distance_right, + mods[0], mods[1]); + g_object_unref (pixbuf); + + g_value_take_boxed (value, slice); + return TRUE; +} + +static gboolean +enum_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_ref (G_VALUE_TYPE (value)); + enum_value = g_enum_get_value_by_nick (enum_class, str); + + if (!enum_value) + { + g_set_error (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_FAILED, + "Unknown value '%s' for enum type '%s'", + str, g_type_name (G_VALUE_TYPE (value))); + g_type_class_unref (enum_class); + return FALSE; + } + + g_value_set_enum (value, enum_value->value); + g_type_class_unref (enum_class); + return TRUE; +} + +static char * +enum_value_to_string (const GValue *value) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + char *s; + + enum_class = g_type_class_ref (G_VALUE_TYPE (value)); + enum_value = g_enum_get_value (enum_class, g_value_get_enum (value)); + + s = g_strdup (enum_value->value_nick); + + g_type_class_unref (enum_class); + + return s; +} + +static gboolean +flags_value_from_string (const char *str, + GFile *base, + GValue *value, + GError **error) +{ + GFlagsClass *flags_class; + GFlagsValue *flag_value; + guint flags = 0; + char **strv; + guint i; + + strv = g_strsplit (str, ",", -1); + + flags_class = g_type_class_ref (G_VALUE_TYPE (value)); + + for (i = 0; strv[i]; i++) + { + strv[i] = g_strstrip (strv[i]); + + flag_value = g_flags_get_value_by_nick (flags_class, strv[i]); + if (!flag_value) + { + g_set_error (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME, + "Unknown flag value '%s' for type '%s'", + strv[i], g_type_name (G_VALUE_TYPE (value))); + g_type_class_unref (flags_class); + return FALSE; + } + + flags |= flag_value->value; + } + + g_strfreev (strv); + g_type_class_unref (flags_class); + + g_value_set_enum (value, flags); + + return TRUE; +} + +static char * +flags_value_to_string (const GValue *value) +{ + GFlagsClass *flags_class; + GString *string; + guint i, flags; + + flags_class = g_type_class_ref (G_VALUE_TYPE (value)); + flags = g_value_get_flags (value); + string = g_string_new (NULL); + + for (i = 0; i < flags_class->n_values; i++) + { + GFlagsValue *flags_value = &flags_class->values[i]; + + if (flags & flags_value->value) + { + if (string->len != 0) + g_string_append (string, ", "); + + g_string_append (string, flags_value->value_nick); + } + } + + g_type_class_unref (flags_class); + + return g_string_free (string, FALSE); +} + +/*** API ***/ + +static void +css_string_funcs_init (void) +{ + if (G_LIKELY (from_string_funcs != NULL)) + return; + + from_string_funcs = g_hash_table_new (NULL, NULL); + to_string_funcs = g_hash_table_new (NULL, NULL); + + register_conversion_function (GDK_TYPE_RGBA, + rgba_value_from_string, + rgba_value_to_string); + register_conversion_function (GDK_TYPE_COLOR, + color_value_from_string, + color_value_to_string); + register_conversion_function (GTK_TYPE_SYMBOLIC_COLOR, + symbolic_color_value_from_string, + symbolic_color_value_to_string); + register_conversion_function (PANGO_TYPE_FONT_DESCRIPTION, + font_description_value_from_string, + font_description_value_to_string); + register_conversion_function (G_TYPE_BOOLEAN, + boolean_value_from_string, + boolean_value_to_string); + register_conversion_function (G_TYPE_INT, + int_value_from_string, + int_value_to_string); + register_conversion_function (G_TYPE_UINT, + uint_value_from_string, + uint_value_to_string); + register_conversion_function (G_TYPE_DOUBLE, + double_value_from_string, + double_value_to_string); + register_conversion_function (G_TYPE_FLOAT, + float_value_from_string, + float_value_to_string); + register_conversion_function (GTK_TYPE_THEMING_ENGINE, + theming_engine_value_from_string, + theming_engine_value_to_string); + register_conversion_function (GTK_TYPE_ANIMATION_DESCRIPTION, + animation_description_value_from_string, + animation_description_value_to_string); + register_conversion_function (GTK_TYPE_BORDER, + border_value_from_string, + border_value_to_string); + register_conversion_function (GTK_TYPE_GRADIENT, + gradient_value_from_string, + gradient_value_to_string); + register_conversion_function (CAIRO_GOBJECT_TYPE_PATTERN, + pattern_value_from_string, + NULL); + register_conversion_function (GTK_TYPE_9SLICE, + slice_value_from_string, + NULL); + register_conversion_function (G_TYPE_ENUM, + enum_value_from_string, + enum_value_to_string); + register_conversion_function (G_TYPE_FLAGS, + flags_value_from_string, + flags_value_to_string); +} + +gboolean +_gtk_css_value_from_string (GValue *value, + GFile *base, + const char *string, + GError **error) +{ + FromStringFunc func; + + g_return_val_if_fail (string != NULL, FALSE); + g_return_val_if_fail (string[0] != 0, FALSE); + + css_string_funcs_init (); + + func = g_hash_table_lookup (from_string_funcs, + GSIZE_TO_POINTER (G_VALUE_TYPE (value))); + if (func == NULL) + func = g_hash_table_lookup (from_string_funcs, + GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value)))); + + if (func == NULL) + { + g_set_error (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Cannot convert to type '%s'", + g_type_name (G_VALUE_TYPE (value))); + return FALSE; + } + + return (*func) (string, base, value, error); +} + +char * +_gtk_css_value_to_string (const GValue *value) +{ + ToStringFunc func; + + css_string_funcs_init (); + + func = g_hash_table_lookup (to_string_funcs, + GSIZE_TO_POINTER (G_VALUE_TYPE (value))); + if (func == NULL) + func = g_hash_table_lookup (to_string_funcs, + GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value)))); + + if (func) + return func (value); + + return g_strdup_value_contents (value); +} + +GtkSymbolicColor * +_gtk_css_parse_symbolic_color (const char *str, + GError **error) +{ + GtkSymbolicColor *color; + gchar *end; + + color = symbolic_color_parse_str (str, &end); + + if (*end != '\0') + { + if (color) + { + gtk_symbolic_color_unref (color); + color = NULL; + } + + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Failed to parse symbolic color"); + } + + return color; +} + +GFile * +_gtk_css_parse_url (GFile *base, + const char *str, + char **end, + GError **error) +{ + gchar *path, *chr; + GFile *file; + + if (g_str_has_prefix (str, "url")) + { + str += strlen ("url"); + SKIP_SPACES (str); + + if (*str != '(') + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Expected '(' after 'url'"); + return NULL; + } + + chr = strchr (str, ')'); + if (!chr) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "No closing ')' found for 'url'"); + return NULL; + } + + if (end) + *end = chr + 1; + + str++; + SKIP_SPACES (str); + + if (*str == '"' || *str == '\'') + { + const gchar *p; + p = str; + + str++; + chr--; + SKIP_SPACES_BACK (chr); + + if (*chr != *p || chr == p) + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "Did not find closing quote for url"); + return NULL; + } + } + else + { + g_set_error_literal (error, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE, + "url not properly escaped"); + return NULL; + } + + path = g_strndup (str, chr - str); + g_strstrip (path); + } + else + { + path = g_strdup (str); + if (end) + *end = (gchar *) str + strlen (str); + } + + file = g_file_resolve_relative_path (base, path); + g_free (path); + + return file; +} diff --git a/gtk/gtkcssstringfuncsprivate.h b/gtk/gtkcssstringfuncsprivate.h new file mode 100644 index 0000000000..900d38c2da --- /dev/null +++ b/gtk/gtkcssstringfuncsprivate.h @@ -0,0 +1,43 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2010 Carlos Garnacho + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_CSS_STRINGFUNCS_PRIVATE_H__ +#define __GTK_CSS_STRINGFUNCS_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +gboolean _gtk_css_value_from_string (GValue *value, + GFile *base, + const char *string, + GError **error); +char * _gtk_css_value_to_string (const GValue *value); + +GtkSymbolicColor * _gtk_css_parse_symbolic_color (const char *str, + GError **error); + +GFile * _gtk_css_parse_url (GFile *base, + const char *str, + char **end, + GError **error); + +G_END_DECLS + +#endif /* __GTK_CSS_STRINGFUNCS_PRIVATE_H__ */ -- 2.30.2